# -*- coding:utf-8 -*-

# 数据存储

## txt文本存储
"""
'''
1. 目标: 保存知乎上发现页面的热门话题部分,将问题和答案保存成文本.
2. 用requests将网页源代码获取下来,用pyquery解析库解析.然后提取标题,回答者,回答保存到文本.
'''
import requests
from pyquery import PyQuery as pq 

url = 'https://www.zhihu.com/explore'
headers = {
    'User-Agent': 'Mozilla/5.0 (X11; Linux x86_64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/65.0.3325.146 Safari/537.36'
}
html = requests.get(url,headers=headers).text
doc = pq(html)
items = doc(' .explore-tab .feed-item').items()
for item in items:
    question = item.find('h2').text()
    author = item.find('.author-link-line').text()
    answer = pq(item.find('.content').html()).text()
    file = open('./text/explore.txt','a',encoding='utf-8')
    file.write('\n'.join([question,author,answer]))
    file.write('\n' + '=' * 50 + '\n')
    file.close()
'''
说明: 用requests提取知乎的发现页面,然后将热门话题的问题,回答者,答案全文提取出来,然后利用Python提供的open()方法打开一个文本文件,获取一个文件操作对象,这里赋值为file,接着利用file对象的write()方法将提取的内容写入文件,最后调用close()方法将其关闭.

open()方法的第一个参数即要保存的目标文件名称,第二个参数为a,代表以追加方式写到文本, 指定文件编码,写入完成后,调用close()方法来关闭文件对象.
'''
"""



# 读取JSON
# 调用JSON库的loads()方法将JSON文本字符串转为JSON对象
# 调用JSON库的dumps()方法将JSON对象转为文本字符串.
import json
"""
str = '''
[{
    "name":"Bob",
    "gender":"mail",
    "birthday":"1992-10"
},{
    "name": "selina",
    "gender": "female",
    "birthday": "1995"
}]
'''

print(type(str))
data = json.loads(str)
print(data)
print(type(data))
# 使用loads()方法将字符串转为JSON对象,由于最外层是中括号,最终类型为列表类型.
# 用索引获取对应内容:
print(data[1]['name'])
print(data[0].get('gender'))
print(data[0].get('age'))  # age在原字典中该键名不存在,返回None.
print(data[0].get('age',25)) # 传入第二个参数25,在不存在时,返回该默认值.


# 从JSON文本中读取内容,先将文本文件内容读出,再利用loads()方法转化.
with open('./text/data.json','r') as file:
    str = file.read()
    data = json.loads(str)
    print(data)
"""



# 输出JSON
# 调用dumps()方法将JSON对象转化为字符串
# 保存JSON格式,添加一个参数indent,代表缩进字符个数.
"""
data = [{
    'name':'Bob',
    'gender': 'male',
    'birthday': '1092'
}]
with open('./text/data.json','a+') as file:
    # file.write(json.dumps(data))
# 调用dumps()方法将JSON对象转化为字符串.再调用文件的write()方法写入文件.
    file.write(json.dumps(data,indent=2))
# 保存JSON格式,添加一个参数indent,代表缩进字符个数.



# json中的中文字符,处理?
data = [{
    'name': '王三',
    'gender': '男',
    'birthday': '1993'
}]
# with open('./text/chinese.json','w') as file:
    # file.write(json.dumps(data,indent=2))  
    # 结果为: 中文字符变成了Unicode字符.

# 要想输出中文,先规定文件输出的编码(encoding参数),文件写入时需要指定参数(ensure_ascii=False)
with open('./text/chinese.json','a+',encoding='utf-8') as file:
    file.write(json.dumps(data,indent=2,ensure_ascii=False))
"""


# CSV文本存储

# 写入
import csv
"""
with open('./text/data.csv','w+') as csvfile:
    writer = csv.writer(csvfile,delimiter=' ')
    writer.writerow(['id','name','age'])
    writer.writerow(['101','yuan',20])
    writer.writerow(['102','guo',19])
    writer.writerow(['103','ding',22])
'''
打开data.csv文件,指定打开模式为w,获得文件句柄,再调用csv库的writer()方法初始化写入对象,传入该句柄.然后调用writerow()方法传入每行的数据即可完成写入. 在初始化对象时传入delimiter为空格,输出结果就是以空格分隔.
'''


# 调用writerows()方法同时写入多行,此时参数需要为二维列表.
with open('./text/data.csv','w') as csvfile:
    writer = csv.writer(csvfile)
    writer.writerow(['id','name','age'])
    writer.writerows([['1','yuan',25],['2','mike',26]])


with open('./text/data.csv','w',encoding='utf-8') as csvfile:
    filenames = ['id','name','age']
    writer = csv.DictWriter(csvfile,fieldnames=filenames)
    writer.writeheader()
    writer.writerow({'id': '1','name':'yuan','age': 25})
    writer.writerow({'id':'2','name':'vicky','age':27})
    writer.writerow({'id':'3','name':'phil','age':28})
    writer.writerow({'id':'4','name': '宾','age': 19})
    
'''
先定义3个字段,用filenames字段表示,然后将其传给DictWriter来初始化一个字典写入对象,接着可以调用writeheader()方法先写入头信息,再调用writerow()方法传入相应字典即可.
'''
"""


# 追加写入,修改文件的打开模式为a.
# 写入中文内容时,需要给open()参数指定编码格式.
"""
with open('./text/onedata.csv','a',encoding='utf-8') as csvfile:
    fieldnames = ['id','name','age']
    writer = csv.DictWriter(csvfile,fieldnames=fieldnames)
    writer.writerow({'id':'1','name':'原','age': 25})
    # 需要给open()函数指定编码,否则发生编码错误.
"""



# 读取
"""
with open('./text/data.csv','r',encoding='utf-8') as csvfile:
    reader = csv.reader(csvfile)
    for row in reader:
        print(row)
# 构造的是Reader对象,通过遍历输出了每行的内容,每一行都是一个列表形式. CSV文件包含中文,需要指定文件编码.

# 利用pandas库的read_csv()方法将数据从csv文件中读取出来.
import pandas as pd 

df = pd.read_csv('./text/data.csv')
print(df)
"""


## pymysql使用
# 连接数据库,查询版本,创建spiders数据库
# import pymysql

"""
db = pymysql.connect(host='172.20.10.2',user='root',password='123.cn',port=3310,charset='utf8mb4')
cursor = db.cursor()
cursor.execute('SELECT VERSION()')
data = cursor.fetchone()
print('mysql version:',data)
cursor.execute("CREATE DATABASE spiders")
db.close()
'''
通过connect()方法声明一个mysql连接对象db,传入参数有: host,user,password,port,charset等.
连接成功后,再调用cursor方法获得mysql的操作游标,利用游标来执行sql语句. 直接用execute()方法执行即可. 第一句SQL语句获得mysql版本. 
调用fetchone()方法获得第一条数据. 第二句SQL语句执行创建spiders数据库.
'''
"""


# 创建表
# db = pymysql.connect(host='172.20.10.2',user='root',password='123.cn',port=3310,db='spiders',charset='utf8mb4')
# cursor = db.cursor()

"""
sql = 'CREATE TABLE IF NOT EXISTS students(id VARCHAR(255)NOT NULL,name VARCHAR(255)NOT NULL,age INT NOT NULL,PRIMARY KEY(id))'
cursor.execute(sql)
db.close()
"""



# 插入数据
"""
id = '1'
user = 'yuan'
age = 25

db = pymysql.connect(host='172.20.10.2',user='root',password='123.cn',port=3310,db='spiders',charset='utf8mb4')
sql = 'INSERT INTO students(id,name,age) values(%s,%s,%s)'
try:
    cursor.execute(sql,(id,user,age))
    db.commit()
except:
    db.rollback()
db.close()
'''
直接用格式化符%s实现,然后在execute()方法的第一个参数传入该SQL语句,value值用统一的元组传过来.
需要执行db对象的commit()方法可以实现数据插入.这个方法是真正将SQL语句提交到数据库执行的方法.对于数据插入,更新,删除操作,都需要调用该方法才能生效.
异常处理,如果执行失败,调用rollback()执行数据回滚.
'''
"""

# 事务机制的特性: ACID特性(原子性,一致性,隔离性,持久性)

# SQL语句根据字典动态构造,元组也动态构造.实现通用的插入方法.
"""
db = pymysql.connect(host='172.20.10.2',user='root',password='123.cn',port=3310,db='spiders')

data = {
    'id':'2',
    'name': 'ding',
    'age': 20
}
table = 'students'

keys = ', '.join(data.keys())
values = ', '.join(['%s'] * len(data))
sql = 'INSERT INTO {table}({keys})VALUES({values})'.format(table=table,keys=keys,values=values)
try:
    if cursor.execute(sql,tuple(data.values())):
        print('Successful')
        db.commit()
except:
    print('Failed')
    db.rollback()
db.close()
'''
传入数据是字典,并将其定义为data变量,表名也定义成变量table.

首先,构造插入的字段id,name,age, 将data键名拿过来,再用逗号分隔即可. 所以', '.join(data.keys())结果是id,name,age,需要构造多个%s当作占位符,有几个字段构造几个即可.这里有三个字段,需要构造%s,%s,%s.
首先定义了长度为1的数组['%s'],再用乘法将其扩充为['%s','%s','%s'],再调用join()方法,最终变成%s,%s,%s. 
最后再利用字符串的format()方法将表名,字段名和占位符构造出来,最终的SQL语句就被构造成了:INSERT INTO students(id,name,age)values(%s,%s,%s)

最后,为execute()方法的第一个参数传入sql变量,第二个参数传入data的键值构造的元组,就可以成功插入数据了.
'''
"""

# 更新数据
# 数据更新操作实际也是执行SQL语句
"""
db = pymysql.connect(host='172.20.10.2',user='root',password='123.cn',port=3310,db='spiders',charset='utf8mb4')

sql = 'UPDATE students SET age = %s WHERE name = %s'
try:
    cursor.execute(sql,(25,'jie'))
    db.commit()
except:
    db.rollback()

db.close()
"""
'''
用占位符的方式构造SQL,然后执行execute()方法,传入元组形式的参数,执行commit()方法执行操作.
'''


# 需要再实现一种去重方法,如果数据存在,则更新数据.如果数据不存在,则插入数据,这种做法执行灵活的字典传值.
"""
db = pymysql.connect(host='172.20.10.2',user='root',password='123.cn',port=3310,db='spiders',charset='utf8mb4')

cursor = db.cursor()

data = {
    'id': '1',
    'name': 'yuan',
    'age': 19
}
table = 'students'
keys = ', '.join(data.keys())
values = ', '.join(['%s'] * len(data))

sql = 'INSERT INTO {table}({keys}) VALUES ({values}) ON DUPLICATE KEY UPDATE'.format(table=table,keys=keys,values=values)
update = ','.join([" {key} = %s".format(key=key) for key in data])
sql += update
try:
    if cursor.execute(sql,tuple(data.values())*2):
        print('successful')
        db.commit()
except:
    print('Failed')
    db.rollback()

db.close()
'''
这里构造的SQL语句是插入语句,在后面加了ON DUPLICATE KEY UPDATE,这行代码意思是如果主键已经存在,就执行更新操作. 
完整SQL语句构造:
    INSERT INTO students(id,name,age)VALUES(%s,%s,%s) ON DUPLICATE KEY UPDATE id=%s,name=%s,age=%s.
这里变成了6个%s,在后面的execute()方法的第二个参数就乘以2变成原来的2倍.
'''
"""

# 删除数据
# 使用DELETE语句即可,需要指定要删除的目标表名和删除条件.
"""
table = 'students'
condition = 'age < 20'

sql = "DELETE FROM {table} WHERE {condition}".format(table=table,condition=condition)
# sql = "DELETE FROM students WHERE age < 20"
try:
    cursor.execute(sql)
    db.commit()
    print('successful')
except:
    db.rollback()
    print('Failed')

db.close()
# 删除条件,运算符>,=,LIKE,<等.
"""


# 查询数据
"""
sql = 'SELECT * FROM students WHERE age >= 20'

try:
    cursor.execute(sql)
    print('Count:',cursor.rowcount)
    one = cursor.fetchone()
    print('One:',one)
    results = cursor.fetchall()
    print('results:',results)
    print('results type:',type(results))
    for row in results:
        print(row)
except:
    print('error')
'''
查询20岁以上的学生,将其传给execute()方法. 这里不需要db的commit方法. 调用cursor的rowcount属性获取查询结果的条数.
调用fetchone方法,获取结果的第一条数据,返回结果为元组. 元组的元素顺序跟字段一一对应. 
调用fetchall()方法,得到结果的所有数据,将结果和类型打印出来.是二重元组, 将其遍历输出.
注意: fetchall方法的内部实现有一个偏移指针用来指向查询结果,最开始偏移指针指向第一条数据,取一次后,指针偏移到下一条数据,再取的话,就取到下一条数据了. 
最初调用了一次fetone方法,这样结果的偏移指针就指向下一条数据,fetchall方法返回的是偏移指针指向的数据一直到结束的所有数据.
'''
"""


# 用while循环加fetchone()方法来获取所有数据.fetchall方法将结果以元组形式返回,数据量大时,占用开销高.
"""
sql = 'SELECT * FROM students'
try:
    cursor.execute(sql)
    print('Count:',cursor.rowcount)
    row = cursor.fetchone()
    while row:
        print('Row:',row)
        row = cursor.fetchone()
except:
    print('error')

# 每循环一次,指针就偏移一条数据,简单高效.
"""



## MongoDB存储
# 使用pymongo库的MongoClient. 传入MongoDB的IP和端口即可.
import pymongo
client = pymongo.MongoClient(host='172.20.10.2',port=3312)
# 创建MongoDB的连接对象

# 另外MongoClient 的第一个参数可以直接传入MongoDB的连接字符串:
# client = MongoClient('mongodb://172.20.10.2:3311/')

# 1. 指定数据库:
# 可以建立多个数据库,指定操作那个数据库
db = client.test 

# 2. 指定集合: 声明了一个Collection对象.
collection = db.students

# 3. 插入数据
"""
# 插入数据,数据以字典形式表示
student = {
    'id': '2',
    'name': 'yuan',
    'age': 26,
    'gender': 'man'
}

# 调用collection的insert()方法即可插入数据.
result = collection.insert(student)
print(result)
# 每条数据都有一个_id属性来唯一标识,如果没有显式指明该属性, MongoDB会自动产生一个Objectid类型的_id 属性 。insert()方法会在执行后返回_id 值.
"""


# 同时插入多条数据,以列表形式传递即可.
"""
student1 = {
    'id': '3',
    'name': 'John',
    'age': 30,
    'gender': 'male'
}
student2 = {
    'id':'4',
    'name': 'Shen',
    'age': 26,
    'gender': 'man'
}

result = collection.insert([student1,student2])
print(result)
# 返回结果是对应的_id的集合:
# [ObjectId('5c3c089f3d0b501b1c574a1c'), ObjectId('5c3c089f3d0b501b1c574a1d')]
"""

# 官方推荐使用 insert_one ()和 insert_many () 方法来分别插入单条记录和多条记录
"""
student = {
    'id': '5',
    'name': 'Dan',
    'age': 39,
    'gender': 'man'
}
result = collection.insert_one(student)
print(result)
print(result.inserted_id)

'''
输出结果:
<pymongo.results.InsertOneResult object at 0x7fcdb0281e08>
5c3c0a3c3d0b501bc093e1b3
这次返回的是InsertOneResult对象,可以调用其inserted_id属性获取_id.
'''
"""


# 使用insert_many()方法,可以将数据以列表形式传递.
"""
student1 = {
    'id': '6',
    'name':'Vicky',
    'age': 30,
    'gender': 'woman'
}
student2 = {
    'id': '7',
    'name': 'sno',
    'age': 27,
    'gender': 'woman'
}

result = collection.insert_many([student1,student2])
print(result)
print(result.inserted_ids)
'''
输出结果为:
<pymongo.results.InsertManyResult object at 0x7f027ea8b1c8>
[ObjectId('5c3c0bd83d0b501f7189eda9'), ObjectId('5c3c0bd83d0b501f7189edaa')]
该方法返回的类型是InsertManyResult,调用inserted_ids属性可以获取插入数据的_id列表.
'''
"""


# 4. 查询: 使用find_one()或find()方法进行查询. find_one()查询得到的是单个结果,find()则返回一个生成器对象.
"""
result = collection.find_one({'name':'Vicky'})
print(type(result))
print(result)
# 返回的是字典类型.
'''
<class 'dict'>
{'_id': ObjectId('5c3c0bd83d0b501f7189eda9'), 'id': '6', 'name': 'Vicky', 'age': 30,
 'gender': 'woman'}

_id属性,是MongoDB在插入过程中自动添加的.
'''
"""

# 根据ObjectId来查询
"""
from bson.objectid import ObjectId
result = collection.find_one({'_id':ObjectId('5c3c0bd83d0b501f7189eda9')})
print(result)
# 查询得到的结果依然是字典类型.如果查询结果不存在,则会返回 None.
"""

# 多条数据查询,使用find()方法.

"""
# 查找年龄为26的数据:
results = collection.find({'age':26})
print(results)
for result in results:
    print(result)
'''
<pymongo.cursor.Cursor object at 0x7f7f940996d8>
{'_id': ObjectId('5c2dad2c3d0b50331f0e5d19'), 'id': '2', 'name': 'yuan', 'age': 26,
'gender': 'man'}
{'_id': ObjectId('5c3c089f3d0b501b1c574a1d'), 'id': '4', 'name': 'Shen', 'age': 26,'gender': 'man'}

返回结果是Cursor类型,它相当于一个生成器,需要遍历取到所有的结果.其中每个结果都是字典类型.
'''
"""


# 查询年龄大于20的数据:
"""
result = collection.find({'age':{'$gt':26}})
print(result)
for resul in result:
    print(resul)
"""

# 5. 计数
# 计数,统计查询结果有多少条,count方法
"""
count = collection.find().count()
print(count)
print('=='*20)
# 统计符合某个条件的数据:
cout = collection.find({'age':26}).count()
print(cout)
# 输出结果为一个数值,即符合条件的数据条数.
"""
'''
6
========================================
2
'''


# 6. 排序
# 排序时,用sort()方法.并在其中传入排序的字段以及升降序标志即可.
# results = collection.find().sort('name',pymongo.ASCENDING)
# print([result['name'] for result in results])
# 升序pymongo.ASCENDING,降序排列pymongo.DESCENDING
'''
输出结果: ['Dan', 'John', 'Shen', 'Vicky', 'sno', 'yuan']
'''

# 7. 偏移
# 利用skip()方法偏移几个位置.
# 偏移2,就忽略前两个元素,得到第三个及以后的元素.
"""
results = collection.find().sort('name',pymongo.ASCENDING).skip(2)
print([result['name'] for result in results])
'''
输出结果: ['Shen', 'Vicky', 'sno', 'yuan']
'''

# 用limit()方法指定要取的结果个数
results = collection.find().sort('name',pymongo.ASCENDING).skip(2).limit(2)
print([result['name'] for result in results])
'''
输出结果:  ['Shen', 'Vicky']
'''

# 数据库数据庞大时,不要使用偏移量来查询
from bson.objectid import ObjectId
collection.find{'_id': {'$gt': ObjectId('5c3c089f3d0b501b1c574a1d')}}
"""


# 8. 更新
# 使用update()方法,指定更新的条件和更新后的数据.
"""
condition = {'name': 'sno'}
student = collection.find_one(condition)
student['age'] = 25
result = collection.update(condition,student)
print(result)
# 更新name为sno的数据的年龄,先指定查询条件,将数据查询出来,修改年龄后,调用update()方法将原条件和修改后的数据传入.
'''
输出结果: {'n': 1, 'nModified': 1, 'ok': 1.0, 'updatedExisting': True}
返回结果为字典,ok执行成功,nModified代表影响的数据条数.
'''
# 使用`$set`操作符也可以对数据进行更新.
# result = collection.update(condition,{'$set': student})
# 这样可以只更新student字典内存在的字段。 如果原先还有其他字段,则不会更新,也不会删除.
"""


# 使用update_one和update_many方法,第二个参数需要使用$类型操作符作为字典的键名.
"""
condition = {'name':'Shen'}
student = collection.find_one(condition)
student['age'] = 26
result = collection.update_one(condition,{'$set':student})
print(result)
print(result.matched_count,result.modified_count)
# 调用update_one方法,第二个参数不能再直接传入修改后的字典,而是需要使用{'$set': student}形式,返回结果为:UpdateResult类型,然后分别调用matched_count和modified_count属性. 获得匹配的数据条数和影响的数据条数.

con = {'age': {'$gt': 22}}
result = collection.update_one(con,{'$inc': {'age':1}})
print(result)
print(result.matched_count,result.modified_count)
# 指定查询条件为年龄大于22,更新条件为{'$inc': {'age':1}},年龄加一,执行之后会将第一条符合条件的数据年龄加一.
'''
<pymongo.results.UpdateResult object at 0x7f086c866f88>
1 1
'''

# 调用update_many()方法,会将所有符合条件的数据都更新
cond = {'age': {'$gt':20}}
result = collection.update_many(cond,{'$inc':{'age':1}})
print(result)
print(result.matched_count,result.modified_count)
'''
<pymongo.results.UpdateResult object at 0x7f753e5b1e48>
6 6
匹配条数为6条,将所有匹配到的数据都更新了.
'''
"""


# 9. 删除
# 调用`remove()`方法指定删除的条件
"""
result = collection.remove({'name': 'sno'})
print(result)
'''
输出结果: {'n': 1, 'ok': 1.0}
'''

# delete_one()和delete_many()方法
# delete_one()即删除第一条符合条件的数据,delete_many()即删除所有符合条件的数据. 返回结果都是DeleteResult类型,可以调用deleted_count属性获取删除的数据条数.
result = collection.delete_one({'name':'Shen'})
print(result)
print(result.deleted_count)
result = collection.delete_many({'age': {'$lt':25}})
print(result.deleted_count)
'''
<pymongo.results.DeleteResult object at 0x7f7c02f78e08>
1
0
'''
"""


